//////////////////////////////////////////////////////////////////////////////
/// @file      main.js
/// @author    SENOO, Ken
/// @copyright CC0
//////////////////////////////////////////////////////////////////////////////

"use strict";

document.querySelector('#button').onclick = async function() {
  let input = new URL(document.querySelector('#input').value);
  const id = input.pathname.replace(/\/+$/, '').replace(/.*\//, '');
  const handle = document.querySelector('#handle').value;
  const password = document.querySelector('#password').value;
  let post;
  do {
    const funcs = [getMisskey, getMastodon, getTwitter, getATProtocol];
    for (const key in funcs) {
      if (post = await funcs[key](input, id, handle, password)) {
        break;
      }
    }
  } while (false);
  format(post);
  return;

  function removeScheme(url) {
    return url.replace(/^[^:]*:/, '');
  }

  function format(post) {
    const bq = document.createElement('blockquote');
    bq.setAttribute('style', `background:${post.color};background-image:`
      + 'linear-gradient(hsla(0,0%,100%,0.5),hsla(0,0%,100%,0.5))');

    function createPost(post) {
      const article = document.createElement('article');

      const header = document.createElement('header');
      header.setAttribute('style', 'display:flex;line-height:1.5');
      const img = document.createElement('img');
      img.setAttribute('referrerpolicy', 'no-referrer');
      img.setAttribute('alt', 'avatar');
      img.setAttribute('src', removeScheme(post.avatar));
      img.setAttribute('style', 'height:4.5em');
      header.appendChild(img);
      let profile = `<div><a href="${post.from.url}">${post.date}</a></div><div
      >${htmlspecialchars(post.from.display_name)}|<a
      href="${post.from.profile_url}">${post.from.acct}</a></div>`;
      if (post.to) {
        profile += `<div><a href="${post.to.url}">replying to</a>
          ${htmlspecialchars(post.to.display_name)}|<a
          href="${post.to.profile_url}">${post.to.acct}</a></div>`;
      }

      header.innerHTML +=
        `<div>${profile.replace(/&nbsp;/g, '&#x00A0;')}</div>`;
      article.appendChild(header);

      const main = document.createElement('main');
      if (post.html) {
        // Mastodon returns invalid XML &nbsp;.
        main.innerHTML = post.html.replace(/&nbsp;/g, '&#x00A0;');
      } else if (post.text) {
        const span = document.createElement('span');
        span.textContent = post.text;
        span.setAttribute('style', 'white-space:pre-wrap;word-wrap:word-break');
        main.appendChild(span);
      }
      if (post.cw) {
        main.innerHTML = `<p>${post.cw}</p>${main.innerHTML}`;
      }
      article.appendChild(main);

      const footer = document.createElement('footer');
      const media_types = {unknown: 'a',
        image: 'img', gifv: 'img', video: 'video', audio: 'audio'};
      for (const key in post.attachments) {
        const media = post.attachments[key];
        if (!media.tag) {
          console.log('No media.');
          continue;
        }
        const el = document.createElement(media.tag);
        el.setAttribute('referrerpolicy', 'no-referrer');
        if (media.alt) {
          el.setAttribute('alt', media.alt);
        }
        const src = media.tag === 'a' ? 'href' : 'src';
        el.setAttribute(src, removeScheme(media.url));
        if (media.tag === 'a') {
          el.textContent = 'attachment' + key;
        }
        if (media.width) {
          el.setAttribute('width', media.width);
        }
        footer.appendChild(el);
      }
      if (footer.hasChildNodes()) {
        article.appendChild(footer);
      }
      return article;
    }

    let parent = createPost(post);
    if (post.cite) {
      let child = createPost(post.cite);
      child.setAttribute('style', 'border:thin solid');
      parent.getElementsByTagName('main')[0].appendChild(child);
    }

    bq.appendChild(parent);

    // Output
    document.querySelector("#output").textContent = bq.outerHTML;
    document.querySelector("#view").innerHTML = bq.outerHTML;
    navigator.clipboard.writeText(bq.outerHTML);
  }

  async function getMastodon(permalink, id) {
    const gw = permalink.origin + "/api/v1/statuses/"
    const url = new URL(gw + id);
    return await fetch(url.href).then(res => res.json()).then(async data => {
      let post = {
        attachments: [],
        avatar: data.account.avatar,
        // https://joinmastodon.org/
        color: 'rgb(99,100,255)',
        // It seems remote post response <br> (not XML <br /> tag ex: https://mastodon-japan.net/@upasampada@fedibird.com/109776574601158598)
        html: data.content.replace(/\u003cbr\u003e/g, '\u003cbr /\u003e'),
        cw: data.spoiler_text,
        date: data.created_at,
        from: {
          url: data.url,
          acct: data.account.acct +
            (data.account.acct.includes('@') ? '' : '@'+url.host),
          display_name: data.account.display_name,
          profile_url: data.account.url,
        },
      };

      if (data.in_reply_to_id) {
        post.to = await fetch(gw+data.in_reply_to_id)
          .then(res => res.json()).then(data => { return {
            url: data.url,
            acct: data.account.acct +
              (data.account.acct.includes('@') ? '' : '@'+url.host),
            display_name: data.account.display_name
              ? data.account.display_name : data.account.username,
            profile_url: data.account.url,
          }}).catch(e => console.log(e));
      }

      const media_types = {unknown: 'a',
        image: 'img', gifv: 'img', video: 'video', audio: 'audio'};
      for (const key in data.media_attachments) {
        const media = data.media_attachments[key];
        const attachment = {
          tag: media_types[media.type],
          url: media.url,
        };
        if (media.meta) {
          if (media.meta.description) {
            attachment.alt = media.meta.description;
          }
          if (media.meta.small) {
            attachment.width = media.meta.small.width;
          }
        }
        post.attachments.push(attachment);
      }
      return post;
    }).catch(e => console.log(e));
  }

  async function getMisskey(permalink, id) {
    const gw = new URL(permalink.origin + '/api/notes/show');
    return await fetch(gw.href, {method: 'POST', body: `{"noteId": "${id}"}`,
      headers: {'Content-Type': 'application/json'}}
    ).then(res => res.json()).then(async data => {
      function getMP(data) {
        let host = data.user.host ? data.user.host : gw.host;
        let post = {
          attachments: [],
          avatar: data.user.avatarUrl,
          // https://github.com/misskey-dev/misskey/blob/develop/packages/backend/assets/icons/192.png
          color: 'rgb(158,194,63)',
          text: data.text,
          cw: data.cw,
          date: data.createdAt,
          from: {
            url: `${gw.origin}/notes/${data.id}`,
            acct: `${data.user.username}@${host}`,
            display_name: data.user.name ? data.user.name : data.user.username,
            profile_url: `${gw.origin}/@${data.user.username}@${host}`,
          },
        };

        if (data.reply) {
          const host = data.reply.user.host ? data.reply.user.host : gw.host;
          post.to = {
            url: `${gw.origin}/notes/${data.reply.id}`,
            acct: `${data.reply.user.username}@${host}`,
            display_name: data.reply.user.name
              ? data.reply.user.name : data.reply.user.username,
            profile_url: `${gw.origin}/@${data.reply.user.username}@${host}`,
          };
        }

        for (const key in data.files) {
          const media = data.files[key];
          const attachment = {url: media.url};
          if ((""+media.type).startsWith('image')) {
            attachment.tag = 'img';
          } else if ((""+media.type).startsWith('video')) {
            attachment.tag = 'video';
          } else if ((""+media.type).startsWith('audio')) {
            attachment.tag = 'audio';
          } else if (media.type) {
            attachment.tag = 'a';
          }
          if (media.name) {
            attachment.alt = media.name;
          }
          if (media.properties) {
            attachment.width = media.properties.width;
          }
          post.attachments.push(attachment);
        }
        return post;
      }

      let post = getMP(data);
      post.cite = data.renote ? getMP(data.renote) : null;
      return post;
    }).catch(e => null);
  }

  async function getTwitter(permalink, id) {
    let gw = permalink.origin + '/api/';
    if (permalink.host === 'twitter.com') {
      gw = 'https://api.twitter.com/1.1/'
    }
    const url = new URL(gw + 'statuses/show.json?id='+ id);
    return await fetch(url.href).then(res => res.json()).then(async data => {
      let post = {
        attachments: [],
        avatar: data.user.profile_image_url,
        color: 'rgb(29,155,240)',
        text: data.text,
        cw: '',
        date: (new Date(data.created_at)).toISOString(),
        from: {
          url: url.origin + '/notice/' + data.id,
          acct: `${data.user.screen_name}@${url.host}`,
          display_name: data.user.name,
          profile_url: url.origin + '/' + data.user.screen_name,
        },
      };

      if (data.in_reply_to_status_id) {
        post.to = {
          url: url.origin + '/notice/' + data.in_reply_to_status_id,
          acct: data.in_reply_to_screen_name + '@' + url.host,
          profile_url: url.origin + '/' + data.in_reply_to_screen_name,
        };
        post.to.display_name = await fetch(
          gw+'users/show.json?screen_name='+data.in_reply_to_screen_name)
          .then(res => res.json()).then(data => data.name)
          // It seems GNU social does not support remote user.
          // And return fail back for deleted user.
          .catch(e => data.in_reply_to_screen_name);
      }
      return post;
    }).catch(e => null);
  }

  async function getATProtocol(permalink, id, handle, password) {
    const gw = 'https://bsky.social/xrpc/'
    const jwt = await fetch(gw + 'com.atproto.server.createSession',
      {method: 'POST', headers: {'Content-Type': 'application/json'},
        body: `{"identifier":"${handle}", "password":"${password}"}`})
      .then(res => res.json()).then(async data => data.accessJwt)
      .catch(e => console.log(e));
    const paths = permalink.pathname.split('/');
    const did = await fetch(gw + 'com.atproto.identity.resolveHandle?handle=' +
      ((paths.length > 1) ? paths[2] : ''))
      .then(res => res.json()).then(async data => data.did)
      .catch(e => console.log(e));
    const uri = 'at://' + did + '/app.bsky.feed.post/' + paths.slice(-1);
    return await fetch(gw + 'app.bsky.feed.getPostThread?depth=0&uri=' + uri,
      {headers: {'Authorization':`Bearer ${jwt}`}})
      .then(res => res.json()).then(async data => {
        const appUrl = 'https://bsky.app/profile/';
        let post = {
          attachments: [],
          avatar: data.thread.post.author.avatar ?
            data.thread.post.author.avatar : '',
          // bsky.social default banner color.
          color: 'rgb(0,112,255)',
          text: data.thread.post.record.text,
          cw: '',
          date: data.thread.post.record.createdAt,
          from: {
            url: permalink,
            acct: '@' + data.thread.post.author.handle,
            display_name: data.thread.post.author.displayName ?
              data.thread.post.author.displayName
              : data.thread.post.author.handle,
            profile_url: appUrl + data.thread.post.author.handle,
          },
        };

        if (data.thread.parent) {
          post.to = {
            url: appUrl + data.thread.parent.post.author.handle + '/post/' +
              data.thread.parent.post.uri.split('/').slice(-1),
            acct: '@' + data.thread.parent.post.author.handle,
            profile_url: appUrl + data.thread.parent.post.author.handle,
            display_name: data.thread.parent.post.author.displayName ?
              data.thread.parent.post.author.displayName
              : data.thread.parent.post.author.handle,
          };
        }

        function getAttachmentsFromEmbeds(embeds) {
          let attachments = [];
          for (const eKey in embeds) {
            const embed = embeds[eKey];
            if (embed['$type'] === 'app.bsky.embed.images#view') {
              attachments = getAttachmentsFromImages(embed.images);
            } else
            if (embed['$type'] === 'app.bsky.embed.recordWithMedia#view') {
              attachments = getAttachmentsFromImages(embed.media.images);
            }
          }
          return attachments;
        }

        // app.bsky.embed.images#view
        function getAttachmentsFromImages(images) {
          let attachments = [];
          for (const iKey in images) {
            const image = images[iKey];
            const attachment = {
              tag: 'img',
              url: image.thumb,
            };
            if (image.alt) {
              attachment.alt = image.alt;
            }
            attachments.push(attachment);
          }
          return attachments;
        }

        // Embed (attachment/quote etc.)
        if (data.thread.post.embed) {
          const embed = data.thread.post.embed;
          // Attachments
          if (embed.media &&
              (embed.media['$type'] === 'app.bsky.embed.images#view')) {
            post.attachments = getAttachmentsFromImages(embed.media.images);
          }
          // Quote post
          let record = embed.record;
          if (embed['$type'] === 'app.bsky.embed.recordWithMedia#view') {
            record = embed.record.record;
          }
          if (record) {
            if (record.value) {
              post.cite = {
                attachments: [],
                avatar: record.author.avatar ? record.author.avatar : '',
                color: 'rgb(0,112,255)',
                text: record.value.text,
                cw: '',
                date: record.value.createdAt,
                from: {
                  url: appUrl+record.author.handle+'/post/'+
                    record.uri.split('/').slice(-1),
                  acct: '@' + record.author.handle,
                  display_name: record.author.displayName ?
                    record.author.displayName : record.author.handle,
                  profile_url: appUrl + record.author.handle,
                },
              };
              post.cite.attachments = getAttachmentsFromEmbeds(record.embeds);
            }
          }
        }
        return post;
    }).catch(e => console.log(e));
  }

  /// @sa https://pisuke-code.com/javascript-imple-htmlspecialchars/
  /// @sa http://var.blog.jp/archives/77442554.html
  function htmlspecialchars(unsafeText){
    if (typeof unsafeText !== 'string') {
      return unsafeText;
    }
    let escaped = document.createElement('div');
    escaped.textContent = unsafeText;
    console.log(escaped.innerHTML);
    return escaped.innerHTML;
  }
};

